﻿using System.Collections.Generic;

namespace Loki.Utils
{
    public sealed class WeakKeyComparer<T> : IEqualityComparer<object>
    where T : class
    {
        private IEqualityComparer<T> _Comparer;

        internal WeakKeyComparer(IEqualityComparer<T> P_Comparer)
        {
            if (P_Comparer == null)
            {
                _Comparer = EqualityComparer<T>.Default;
            }
            else
            {
                this._Comparer = P_Comparer;
            }
        }

        public int GetHashCode(object P_Obj)
        {
            WeakKeyReference<T> L_WeakKey = P_Obj as WeakKeyReference<T>;
            if (L_WeakKey != null)
            {
                return L_WeakKey.KeyHashCode;
            }

            return this._Comparer.GetHashCode((T)P_Obj);
        }

        // Note: There are actually 9 cases to handle here.
        //
        //  Let Wa = Alive Weak Reference
        //  Let Wd = Dead Weak Reference
        //  Let S  = Strong Reference
        //  
        //  x  | y  | Equals(x,y)
        // -------------------------------------------------
        //  Wa | Wa | comparer.Equals(x.Target, y.Target) 
        //  Wa | Wd | false
        //  Wa | S  | comparer.Equals(x.Target, y)
        //  Wd | Wa | false
        //  Wd | Wd | x == y
        //  Wd | S  | false
        //  S  | Wa | comparer.Equals(x, y.Target)
        //  S  | Wd | false
        //  S  | S  | comparer.Equals(x, y)
        // -------------------------------------------------
        public new bool Equals(object P_X, object P_Y)
        {
            bool L_XIsDead, L_YIsDead;
            T L_First = GetTarget(P_X, out L_XIsDead);
            T L_Second = GetTarget(P_Y, out L_YIsDead);

            if (L_XIsDead)
            {
                return L_YIsDead ? P_X == P_Y : false;
            }

            if (L_YIsDead)
            {
                return false;
            }

            return this._Comparer.Equals(L_First, L_Second);
        }

        private static T GetTarget(object P_Obj, out bool P_IsDead)
        {
            WeakKeyReference<T> L_Wref = P_Obj as WeakKeyReference<T>;
            T L_Target;
            if (L_Wref != null)
            {
                L_Target = L_Wref.Target;
                P_IsDead = !L_Wref.IsAlive;
            }
            else
            {
                L_Target = (T)P_Obj;
                P_IsDead = false;
            }

            return L_Target;
        }
    }
}
